"""
异常检测器

提供市场异常检测、异常分类、告警功能。
"""

import asyncio
import logging
import math
from datetime import datetime, timedelta
from decimal import Decimal
from enum import Enum
from typing import Dict, List, Optional, Callable, Any, Tuple
from dataclasses import dataclass, field


class AnomalyType(Enum):
    """异常类型"""
    PRICE_SPIKE = "price_spike"             # 价格异常波动
    VOLUME_ANOMALY = "volume_anomaly"       # 成交量异常
    VOLATILITY_SPIKE = "volatility_spike"   # 波动率异常
    LIQUIDITY_CRISIS = "liquidity_crisis"   # 流动性危机
    CORRELATION_BREAK = "correlation_break" # 相关性断裂
    PATTERN_ANOMALY = "pattern_anomaly"     # 模式异常
    DATA_QUALITY = "data_quality"           # 数据质量异常
    SYSTEM_ANOMALY = "system_anomaly"       # 系统异常


@dataclass
class AnomalyAlert:
    """异常告警"""
    anomaly_type: AnomalyType
    symbol: str
    timestamp: datetime
    severity: str  # low, medium, high, critical
    confidence: float  # 0.0 - 1.0
    description: str
    data: Dict[str, Any] = field(default_factory=dict)
    threshold: Optional[float] = None
    actual_value: Optional[float] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return {
            'anomaly_type': self.anomaly_type.value,
            'symbol': self.symbol,
            'timestamp': self.timestamp.isoformat(),
            'severity': self.severity,
            'confidence': self.confidence,
            'description': self.description,
            'data': self.data,
            'threshold': self.threshold,
            'actual_value': self.actual_value
        }


@dataclass
class DetectionConfig:
    """检测配置"""
    # 价格异常检测
    price_spike_threshold: float = 0.05  # 5%
    price_window_size: int = 20
    
    # 成交量异常检测
    volume_spike_threshold: float = 3.0  # 3倍标准差
    volume_window_size: int = 50
    
    # 波动率异常检测
    volatility_threshold: float = 0.02  # 2%
    volatility_window_size: int = 30
    
    # 流动性检测
    liquidity_threshold: float = 0.1  # 10%
    bid_ask_spread_threshold: float = 0.005  # 0.5%
    
    # 相关性检测
    correlation_threshold: float = 0.3  # 相关性变化阈值
    correlation_window_size: int = 100
    
    # 数据质量检测
    missing_data_threshold: float = 0.1  # 10%缺失率
    outlier_threshold: float = 3.0  # 3倍标准差
    
    # 通用配置
    confidence_threshold: float = 0.7  # 置信度阈值
    enable_all_detectors: bool = True


@dataclass
class TimeSeriesData:
    """时间序列数据"""
    timestamps: List[datetime]
    values: List[float]
    
    def __len__(self) -> int:
        return len(self.values)
    
    def get_recent(self, count: int) -> 'TimeSeriesData':
        """获取最近的数据"""
        if count >= len(self.values):
            return self
        return TimeSeriesData(
            timestamps=self.timestamps[-count:],
            values=self.values[-count:]
        )
    
    def calculate_returns(self) -> List[float]:
        """计算收益率"""
        if len(self.values) < 2:
            return []
        
        returns = []
        for i in range(1, len(self.values)):
            if self.values[i-1] != 0:
                ret = (self.values[i] - self.values[i-1]) / self.values[i-1]
                returns.append(ret)
        return returns
    
    def calculate_volatility(self, window: int = 20) -> float:
        """计算波动率"""
        returns = self.calculate_returns()
        if len(returns) < window:
            return 0.0
        
        recent_returns = returns[-window:]
        mean_return = sum(recent_returns) / len(recent_returns)
        variance = sum((r - mean_return) ** 2 for r in recent_returns) / len(recent_returns)
        return math.sqrt(variance)


class AnomalyDetector:
    """异常检测器"""
    
    def __init__(self, config: Optional[DetectionConfig] = None):
        self.config = config or DetectionConfig()
        self.logger = logging.getLogger(__name__)
        
        # 数据存储
        self.price_data: Dict[str, TimeSeriesData] = {}
        self.volume_data: Dict[str, TimeSeriesData] = {}
        self.correlation_matrix: Dict[Tuple[str, str], List[float]] = {}
        
        # 异常历史
        self.anomaly_history: List[AnomalyAlert] = []
        self.alert_handlers: List[Callable[[AnomalyAlert], None]] = []
        
        # 统计信息
        self.detection_stats: Dict[AnomalyType, int] = {
            anomaly_type: 0 for anomaly_type in AnomalyType
        }
        
        self._lock = asyncio.Lock()
    
    async def add_price_data(self, symbol: str, timestamp: datetime, price: float):
        """添加价格数据"""
        async with self._lock:
            if symbol not in self.price_data:
                self.price_data[symbol] = TimeSeriesData([], [])
            
            self.price_data[symbol].timestamps.append(timestamp)
            self.price_data[symbol].values.append(price)
            
            # 限制数据长度
            max_length = max(self.config.price_window_size, 
                           self.config.volatility_window_size,
                           self.config.correlation_window_size) * 2
            
            if len(self.price_data[symbol].values) > max_length:
                self.price_data[symbol].timestamps = self.price_data[symbol].timestamps[-max_length:]
                self.price_data[symbol].values = self.price_data[symbol].values[-max_length:]
    
    async def add_volume_data(self, symbol: str, timestamp: datetime, volume: int):
        """添加成交量数据"""
        async with self._lock:
            if symbol not in self.volume_data:
                self.volume_data[symbol] = TimeSeriesData([], [])
            
            self.volume_data[symbol].timestamps.append(timestamp)
            self.volume_data[symbol].values.append(float(volume))
            
            # 限制数据长度
            max_length = self.config.volume_window_size * 2
            if len(self.volume_data[symbol].values) > max_length:
                self.volume_data[symbol].timestamps = self.volume_data[symbol].timestamps[-max_length:]
                self.volume_data[symbol].values = self.volume_data[symbol].values[-max_length:]
    
    async def detect_anomalies(self, symbol: str) -> List[AnomalyAlert]:
        """检测异常"""
        alerts = []
        
        if self.config.enable_all_detectors:
            # 价格异常检测
            price_alerts = await self._detect_price_anomalies(symbol)
            alerts.extend(price_alerts)
            
            # 成交量异常检测
            volume_alerts = await self._detect_volume_anomalies(symbol)
            alerts.extend(volume_alerts)
            
            # 波动率异常检测
            volatility_alerts = await self._detect_volatility_anomalies(symbol)
            alerts.extend(volatility_alerts)
            
            # 流动性异常检测
            liquidity_alerts = await self._detect_liquidity_anomalies(symbol)
            alerts.extend(liquidity_alerts)
            
            # 数据质量检测
            quality_alerts = await self._detect_data_quality_issues(symbol)
            alerts.extend(quality_alerts)
        
        # 过滤低置信度告警
        filtered_alerts = [
            alert for alert in alerts 
            if alert.confidence >= self.config.confidence_threshold
        ]
        
        # 记录异常历史
        for alert in filtered_alerts:
            await self._record_anomaly(alert)
        
        return filtered_alerts
    
    async def _detect_price_anomalies(self, symbol: str) -> List[AnomalyAlert]:
        """检测价格异常"""
        alerts = []
        
        if symbol not in self.price_data:
            return alerts
        
        data = self.price_data[symbol]
        if len(data) < self.config.price_window_size:
            return alerts
        
        recent_data = data.get_recent(self.config.price_window_size)
        returns = recent_data.calculate_returns()
        
        if not returns:
            return alerts
        
        # 计算统计指标
        mean_return = sum(returns) / len(returns)
        std_return = math.sqrt(sum((r - mean_return) ** 2 for r in returns) / len(returns))
        
        # 检测最新价格变动
        if len(returns) > 0:
            latest_return = returns[-1]
            z_score = abs(latest_return - mean_return) / std_return if std_return > 0 else 0
            
            if abs(latest_return) > self.config.price_spike_threshold or z_score > 3:
                severity = "critical" if abs(latest_return) > 0.1 else "high"
                confidence = min(0.9, z_score / 3) if z_score > 0 else 0.5
                
                alert = AnomalyAlert(
                    anomaly_type=AnomalyType.PRICE_SPIKE,
                    symbol=symbol,
                    timestamp=recent_data.timestamps[-1],
                    severity=severity,
                    confidence=confidence,
                    description=f"{symbol} 价格异常波动 {latest_return:.2%}",
                    data={
                        'return': latest_return,
                        'z_score': z_score,
                        'mean_return': mean_return,
                        'std_return': std_return
                    },
                    threshold=self.config.price_spike_threshold,
                    actual_value=abs(latest_return)
                )
                alerts.append(alert)
        
        return alerts
    
    async def _detect_volume_anomalies(self, symbol: str) -> List[AnomalyAlert]:
        """检测成交量异常"""
        alerts = []
        
        if symbol not in self.volume_data:
            return alerts
        
        data = self.volume_data[symbol]
        if len(data) < self.config.volume_window_size:
            return alerts
        
        recent_data = data.get_recent(self.config.volume_window_size)
        values = recent_data.values
        
        if len(values) < 2:
            return alerts
        
        # 计算统计指标
        mean_volume = sum(values[:-1]) / len(values[:-1])  # 排除最新值
        std_volume = math.sqrt(sum((v - mean_volume) ** 2 for v in values[:-1]) / len(values[:-1]))
        
        # 检测最新成交量
        latest_volume = values[-1]
        if std_volume > 0:
            z_score = abs(latest_volume - mean_volume) / std_volume
            
            if z_score > self.config.volume_spike_threshold:
                severity = "high" if z_score > 5 else "medium"
                confidence = min(0.95, z_score / 5)
                
                alert = AnomalyAlert(
                    anomaly_type=AnomalyType.VOLUME_ANOMALY,
                    symbol=symbol,
                    timestamp=recent_data.timestamps[-1],
                    severity=severity,
                    confidence=confidence,
                    description=f"{symbol} 成交量异常 {z_score:.1f}倍标准差",
                    data={
                        'volume': latest_volume,
                        'mean_volume': mean_volume,
                        'z_score': z_score,
                        'std_volume': std_volume
                    },
                    threshold=self.config.volume_spike_threshold,
                    actual_value=z_score
                )
                alerts.append(alert)
        
        return alerts
    
    async def _detect_volatility_anomalies(self, symbol: str) -> List[AnomalyAlert]:
        """检测波动率异常"""
        alerts = []
        
        if symbol not in self.price_data:
            return alerts
        
        data = self.price_data[symbol]
        if len(data) < self.config.volatility_window_size:
            return alerts
        
        # 计算当前波动率
        current_volatility = data.calculate_volatility(self.config.volatility_window_size)
        
        # 计算历史波动率
        if len(data) >= self.config.volatility_window_size * 2:
            historical_data = data.get_recent(self.config.volatility_window_size * 2)
            historical_volatility = historical_data.calculate_volatility(self.config.volatility_window_size)
            
            # 比较当前和历史波动率
            if historical_volatility > 0:
                volatility_ratio = current_volatility / historical_volatility
                
                if (current_volatility > self.config.volatility_threshold or 
                    volatility_ratio > 2.0):
                    
                    severity = "critical" if volatility_ratio > 3 else "high"
                    confidence = min(0.9, volatility_ratio / 3)
                    
                    alert = AnomalyAlert(
                        anomaly_type=AnomalyType.VOLATILITY_SPIKE,
                        symbol=symbol,
                        timestamp=data.timestamps[-1],
                        severity=severity,
                        confidence=confidence,
                        description=f"{symbol} 波动率异常 {current_volatility:.2%}",
                        data={
                            'current_volatility': current_volatility,
                            'historical_volatility': historical_volatility,
                            'volatility_ratio': volatility_ratio
                        },
                        threshold=self.config.volatility_threshold,
                        actual_value=current_volatility
                    )
                    alerts.append(alert)
        
        return alerts
    
    async def _detect_liquidity_anomalies(self, symbol: str) -> List[AnomalyAlert]:
        """检测流动性异常"""
        alerts = []
        
        # 这里可以根据买卖价差、市场深度等指标检测流动性异常
        # 由于缺少具体的买卖盘数据，这里提供一个框架
        
        if symbol not in self.volume_data:
            return alerts
        
        data = self.volume_data[symbol]
        if len(data) < 10:
            return alerts
        
        # 简单的流动性检测：基于成交量的急剧下降
        recent_volumes = data.get_recent(10).values
        if len(recent_volumes) >= 5:
            recent_avg = sum(recent_volumes[-5:]) / 5
            historical_avg = sum(recent_volumes[:-5]) / len(recent_volumes[:-5])
            
            if historical_avg > 0:
                liquidity_ratio = recent_avg / historical_avg
                
                if liquidity_ratio < self.config.liquidity_threshold:
                    alert = AnomalyAlert(
                        anomaly_type=AnomalyType.LIQUIDITY_CRISIS,
                        symbol=symbol,
                        timestamp=data.timestamps[-1],
                        severity="high",
                        confidence=0.7,
                        description=f"{symbol} 流动性下降 {liquidity_ratio:.1%}",
                        data={
                            'recent_avg_volume': recent_avg,
                            'historical_avg_volume': historical_avg,
                            'liquidity_ratio': liquidity_ratio
                        },
                        threshold=self.config.liquidity_threshold,
                        actual_value=liquidity_ratio
                    )
                    alerts.append(alert)
        
        return alerts
    
    async def _detect_data_quality_issues(self, symbol: str) -> List[AnomalyAlert]:
        """检测数据质量问题"""
        alerts = []
        
        # 检测价格数据质量
        if symbol in self.price_data:
            data = self.price_data[symbol]
            if len(data) >= 10:
                # 检测异常值
                values = data.get_recent(50).values if len(data) >= 50 else data.values
                if values:
                    mean_val = sum(values) / len(values)
                    std_val = math.sqrt(sum((v - mean_val) ** 2 for v in values) / len(values))
                    
                    outliers = [v for v in values if abs(v - mean_val) > self.config.outlier_threshold * std_val]
                    outlier_ratio = len(outliers) / len(values)
                    
                    if outlier_ratio > 0.1:  # 超过10%的异常值
                        alert = AnomalyAlert(
                            anomaly_type=AnomalyType.DATA_QUALITY,
                            symbol=symbol,
                            timestamp=data.timestamps[-1],
                            severity="medium",
                            confidence=0.8,
                            description=f"{symbol} 数据质量异常，异常值比例 {outlier_ratio:.1%}",
                            data={
                                'outlier_count': len(outliers),
                                'total_count': len(values),
                                'outlier_ratio': outlier_ratio
                            },
                            threshold=0.1,
                            actual_value=outlier_ratio
                        )
                        alerts.append(alert)
        
        return alerts
    
    async def _record_anomaly(self, alert: AnomalyAlert):
        """记录异常"""
        async with self._lock:
            self.anomaly_history.append(alert)
            
            # 限制历史记录长度
            if len(self.anomaly_history) > 1000:
                self.anomaly_history = self.anomaly_history[-1000:]
            
            # 更新统计
            self.detection_stats[alert.anomaly_type] += 1
            
            # 调用告警处理器
            for handler in self.alert_handlers:
                try:
                    if asyncio.iscoroutinefunction(handler):
                        await handler(alert)
                    else:
                        handler(alert)
                except Exception as e:
                    self.logger.error(f"告警处理器执行失败: {e}")
    
    def add_alert_handler(self, handler: Callable[[AnomalyAlert], None]):
        """添加告警处理器"""
        self.alert_handlers.append(handler)
    
    def remove_alert_handler(self, handler: Callable[[AnomalyAlert], None]):
        """移除告警处理器"""
        try:
            self.alert_handlers.remove(handler)
        except ValueError:
            pass
    
    async def get_recent_anomalies(self, symbol: Optional[str] = None, 
                                 anomaly_type: Optional[AnomalyType] = None,
                                 hours: int = 24) -> List[AnomalyAlert]:
        """获取最近的异常"""
        async with self._lock:
            cutoff_time = datetime.now() - timedelta(hours=hours)
            
            filtered_anomalies = []
            for anomaly in self.anomaly_history:
                if anomaly.timestamp < cutoff_time:
                    continue
                
                if symbol and anomaly.symbol != symbol:
                    continue
                
                if anomaly_type and anomaly.anomaly_type != anomaly_type:
                    continue
                
                filtered_anomalies.append(anomaly)
            
            return sorted(filtered_anomalies, key=lambda x: x.timestamp, reverse=True)
    
    async def get_statistics(self) -> Dict[str, Any]:
        """获取检测统计"""
        async with self._lock:
            total_detections = sum(self.detection_stats.values())
            
            stats = {
                'total_detections': total_detections,
                'detection_by_type': {
                    anomaly_type.value: count 
                    for anomaly_type, count in self.detection_stats.items()
                },
                'monitored_symbols': {
                    'price_data': len(self.price_data),
                    'volume_data': len(self.volume_data)
                },
                'recent_anomalies_24h': len(await self.get_recent_anomalies(hours=24)),
                'config': {
                    'price_spike_threshold': self.config.price_spike_threshold,
                    'volume_spike_threshold': self.config.volume_spike_threshold,
                    'volatility_threshold': self.config.volatility_threshold,
                    'confidence_threshold': self.config.confidence_threshold
                }
            }
            
            return stats
    
    async def clear_data(self, symbol: Optional[str] = None):
        """清理数据"""
        async with self._lock:
            if symbol:
                self.price_data.pop(symbol, None)
                self.volume_data.pop(symbol, None)
                self.logger.info(f"清理 {symbol} 的异常检测数据")
            else:
                self.price_data.clear()
                self.volume_data.clear()
                self.anomaly_history.clear()
                self.detection_stats = {anomaly_type: 0 for anomaly_type in AnomalyType}
                self.logger.info("清理所有异常检测数据")